Skip to content

fs: avoid crash after tolerated watch ENOENT - #64821

Open
LLagoon3 wants to merge 1 commit into
nodejs:mainfrom
LLagoon3:fix-fs-watch-missing-close
Open

fs: avoid crash after tolerated watch ENOENT#64821
LLagoon3 wants to merge 1 commit into
nodejs:mainfrom
LLagoon3:fix-fs-watch-missing-close

Conversation

@LLagoon3

@LLagoon3 LLagoon3 commented Jul 29, 2026

Copy link
Copy Markdown

Summary

  • Clear the JavaScript FSEvent wrapper from FSWatcher._handle when a
    missing path is tolerated with throwIfNoEntry: false.
  • Update test/parallel/test-fs-watch-enoent.js to exercise
    FSWatcher.close() after giving the native close callback an opportunity
    to run on Linux.
  • Add test/parallel/test-watch-mode-missing-env-file-signal.mjs to verify
    clean SIGINT and SIGTERM exits when an optional env file is missing.

Details

On Linux, uv_fs_event_start() returns UV_ENOENT for a missing path.
FSEventWrap::Start() starts closing the native handle before returning that
error. When throwIfNoEntry is false, FSWatcher._handle previously kept a
reference to the JavaScript FSEvent wrapper. A later FSWatcher.close()
accessed that wrapper's initialized getter after the native FSEventWrap
had been detached, causing the FSEventWrap::GetInitialized() null-wrapper
assertion.

Set FSWatcher._handle to null in the tolerated UV_ENOENT branch. This is
only a JavaScript lifecycle-state transition; it does not close the native
handle a second time or emit a close event.

The Linux regression test uses common.platformTimeout(10), a non-zero
delayed timer, before calling close(). A next tick or immediate can run
before the native close callback and therefore would not reliably exercise
the detached-wrapper state.

The watch-mode regression test starts separate processes for SIGINT and
SIGTERM, waits for watch-mode readiness, and checks for a zero exit code, no
terminating signal, and no native assertion output. It also has readiness and
shutdown timeouts and always cleans up a child on failure.

Testing

  • Official Docker baselines reproduced the native assertion with
    node:24.16.0-slim, node:24.18.0-slim, and node:26.5.0-slim.
  • The pre-fix baseline was also reproduced locally at upstream/HEAD
    41525ab9fcb92d6f487047b024be29a1a96d639c
    with exit code 134.
  • The patch was rebased onto upstream/HEAD
    012ecf51d39d784f1c217f941122717688c31add.
  • test/parallel/test-fs-watch-enoent.js and
    test/parallel/test-watch-mode-missing-env-file-signal.mjs passed
    individually and for 20 repetitions each.
  • The watch-mode regression test passed for 20 repetitions in an Ubuntu 26.04
    container using the patched build.
  • Existing-path, default throwIfNoEntry, immediate-close, repeated-close,
    ref()/unref(), AbortSignal, and standalone watch-mode controls passed.
  • A 2,000-watcher stress check observed 2,000 FSEVENTWRAP initializations and
    2,000 destroys.
  • make lint passed.
  • git diff --check passed.
  • make -j4 test passed on 012ecf51d39d784f1c217f941122717688c31add
    in a clean test environment: 5,790 passed, 0 failed.

Fixes: #64819
Refs: #61870
Refs: #20985

@nodejs-github-bot nodejs-github-bot added fs Issues and PRs related to the fs subsystem / file system. needs-ci PRs that need a full CI run. labels Jul 29, 2026
Signed-off-by: LLagoon3 <115124830+LLagoon3@users.noreply.github.com>
@LLagoon3
LLagoon3 force-pushed the fix-fs-watch-missing-close branch from a8488ea to ea3979c Compare July 29, 2026 15:09
@LLagoon3
LLagoon3 marked this pull request as ready for review July 29, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fs Issues and PRs related to the fs subsystem / file system. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

watch: missing --env-file-if-exists path aborts during signal shutdown on Linux

2 participants